package com.cloudbroker.bcs.platform.dav.configure.define;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cloudbroker.bcs.common.configure.ConfigUtil;
import com.cloudbroker.bcs.common.context.DataContext;
import com.cloudbroker.bcs.common.util.EnvironmentUtils;
import com.cloudbroker.bcs.common.util.StringUtil;
import com.cloudbroker.bcs.common.util.ValueUtil;
import com.cloudbroker.bcs.platform.dav.configure.define.element.Config;
import com.cloudbroker.bcs.platform.dav.configure.define.parser.ConfigureParser;
import com.cloudbroker.bcs.platform.dav.configure.service.PersistenceService;

@SuppressWarnings("unchecked")
public class XMLConfigParser {
    
    private static final Logger LOG = LoggerFactory.getLogger(XMLConfigParser.class);
    
    private static final String CONFIG_PATH = "configure";
    
    private static final String ORI_DATA_SEPERATOR = "\r\n####\r\n";
    
    private static Map<String, ConfigureCatalog> catalogs = new LinkedHashMap<String, ConfigureCatalog>();
    
    private static String getConfigDefineDir() {
        return EnvironmentUtils.getClasspathRoot() + CONFIG_PATH;
    }
    
    private static String getTemplateFilePath(String template) {
        return getConfigDefineDir() + '/' + template;
    }
    
    /**
     * 初始化配置模板分类
     */
    private static synchronized void initationCatalogList() {
        try {
            catalogs.clear();
            SAXReader reader = new SAXReader();
            String catalogPath = getConfigDefineDir() + "/category.xml";
            LOG.info("category path:" + catalogPath);
            Document document = reader.read(new File(catalogPath));
            Element root = document.getRootElement();
            Iterator<Element> catalogIterator = root.elementIterator("catalog");
            boolean isNoTenant = ConfigUtil.isNoTenant();
            while (catalogIterator.hasNext()) {
                Element catalogElement = catalogIterator.next();
                String tenantFlag = catalogElement.attributeValue("tenantFlag");
                if (StringUtils.isBlank(tenantFlag)) {
                    tenantFlag = "both";
                }
                if (isNoTenant) {
                    // 通用
                    if ("generalOnly".equals(tenantFlag) || "both".equals(tenantFlag)) {
                        ConfigureCatalog catalog = paserCatalogElement(catalogElement);
                        catalogs.put(catalog.getId(), catalog);
                    }
                } else {
                    // 某租户
                    if ("tenantOnly".equals(tenantFlag) || "both".equals(tenantFlag)) {
                        ConfigureCatalog catalog = paserCatalogElement(catalogElement);
                        catalogs.put(catalog.getId(), catalog);
                    }
                }
            }
        } catch (Exception e) {
            LOG.error("读取配置列表category.xml出错", e);
        }
    }
    
    private static ConfigureCatalog paserCatalogElement(Element catalogElement) {
        ConfigureCatalog catalog = new ConfigureCatalog();
        List<Generate> generates = new ArrayList<Generate>();
        catalog.setId(catalogElement.attributeValue("id", ""));
        catalog.setName(catalogElement.attributeValue("name", ""));
        catalog.setConfig(catalogElement.attributeValue("config", ""));
        catalog.setDescription(catalogElement.attributeValue("description", catalog.getName()));
        Iterator<Element> generateIterator = catalogElement.elementIterator("generate");
        while (generateIterator.hasNext()) {
            Element generateElement = generateIterator.next();
            Generate generate = paserGeneratorElment(generateElement);
            generates.add(generate);
        }
        catalog.setTemplates(generates);
        catalog.setStatus(diffRuntimeFileVSTemplateFile(catalog.getId(), generates));
        return catalog;
    }
    
    // 配置文件
    private static int diffRuntimeFileVSTemplateFile(String catatlogId, List<Generate> generates) {
        String tenantId = DataContext.getTenantId();
        Map<String, Object> config = PersistenceService.getConfigDataFromZooKeeper(catatlogId, tenantId);
        if (config == null || config.isEmpty()) {
            return 0;
        }
        for (int i = 0; i < generates.size(); i++) {
            Generate generate = generates.get(i);
            String template = generate.getTemplate();
            String templateFilePath = getTemplateFilePath(template);
            
            Map<String, Object> zooKeeperMap = PersistenceService.getConfigDataFromZooKeeper(catatlogId, tenantId);
            
            Properties tempProp = ValueUtil.loadProperties(templateFilePath);
            if (null == tempProp) {
                return 1;
            }
            
            Map<String, Object> configValue = new HashMap<String, Object>();
            
            if (zooKeeperMap != null) {
                int slashIdx = template.lastIndexOf('/');
                if (-1 != slashIdx && slashIdx < template.length() - 1) {
                    String fileName = template.substring(slashIdx + 1);
                    for (Entry<String, Object> entry : zooKeeperMap.entrySet()) {
                        if (entry.getKey().endsWith(".properties") && fileName.equalsIgnoreCase(entry.getKey())) {
                            configValue.putAll((Map<String, Object>) entry.getValue());
                        }
                    }
                }
            }
            
            for (Object key : tempProp.keySet()) {
                if (configValue.get(key) == null) {
                    return 1;
                }
            }
        }
        
        return 2;
    }
    
    //配置文件
    @SuppressWarnings("unused")
    private static int diffRuntimeFileVSTemplateFile(List<Generate> generates) {
        for (int i = 0; i < generates.size(); i++) {
            Generate generate = generates.get(i);
            try {
                if (diffRuntimeFileVSTemplateFile(generate)) {
                    return 1;
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return 0;
            } catch (Exception e) {
                e.printStackTrace();
                return 1;
            }
        }
        return 2;
    }
    
    @SuppressWarnings("rawtypes")
    private static boolean diffRuntimeFileVSTemplateFile(Generate generate) throws FileNotFoundException,
            IOException {
        String template = generate.getTemplate();
        String templateFilePath = getTemplateFilePath(template);
        String runtimeFilePath = ConfigUtil.getFileNameWithTenant(template);
        runtimeFilePath = EnvironmentUtils.findFileInRuntimeConfigDirOrClasspath(template);
        if (StringUtils.isBlank(runtimeFilePath)) {
            return true;
        }
        
        if (template.endsWith(".properties")) {
            Properties rprop = ValueUtil.loadProperties(runtimeFilePath);
            if (null == rprop) {
                return true;
            }
            Properties tprop = ValueUtil.loadProperties(templateFilePath);
            if (null == tprop) {
                return true;
            }
            if (rprop.size() != tprop.size()) {
                return true;
            }
            Iterator<Object> iter = tprop.keySet().iterator();
            while (iter.hasNext()) {
                String key = (String) iter.next();
                if (!rprop.containsKey(key)) {
                    return true;
                }
            }
            return false;
        } else {
            String templateContent = readFromFile(new File(templateFilePath));
            if (null == templateContent) {
                throw new FileNotFoundException("template file not found: " + templateFilePath);
            }
            Map configData = loadConfigData();
            String valuedContent = genValuedContent(templateContent, configData, false);
            
            String runtimeContent = readFromFile(new File(runtimeFilePath));
            
            return !valuedContent.equals(runtimeContent);
        }
    }
    
    private static Generate paserGeneratorElment(Element generateElement) {
        String template = generateElement.attributeValue("template", "");
        Generate generate = new Generate();
        generate.setTemplate(template);
        String filePath = EnvironmentUtils.findFileInRuntimeConfigDirOrClasspath(template);
        if (null == filePath) {
            generate.setModifyTime(0L);
        } else {
            File file = new File(filePath);
            generate.setModifyTime(file.lastModified());
        }
        return generate;
    }
    
    /**
     * 获取配置模板分类
     * 
     * @return
     */
    public static Collection<ConfigureCatalog> getCatalogList() {
        // if (catalogs.size() == 0) {
        initationCatalogList();
        // }
        return catalogs.values();
    }
    
    private static Map<Object, Object> loadConfigData() {
        Map<Object, Object> configData = readOriDataFromFile();
        if (null == configData || configData.isEmpty()) {
            //configData = PersistenceService.loadAllOriConfigDataFromDB();
            LOG.info("use configure data in database");
        } else {
            LOG.info("use configure data in local data file");
        }
        return configData;
    }
    
    public static String getConfigureHTML(String catatlogId) {
        Map<Object, Object> configData = loadConfigData();
        String brokerId = DataContext.getTenantId();
        Map<String, Object> config = PersistenceService.getConfigDataFromZooKeeper(catatlogId, brokerId);
        if (config != null) {

            configData = new HashMap<Object, Object>();

            for (Entry<String, Object> entry : config.entrySet()) {
                String key = entry.getKey();

                if (key.indexOf("properties") != -1) {
                    Map<String, Object> configValue = (Map<String, Object>) entry
                            .getValue();

                    for (Entry<String, Object> value : configValue.entrySet()) {
                        String valueKey = value.getKey();
                        {
                            //valueKey = replaceSpecialKey(valueKey);
                            int index = valueKey.indexOf("$");
                            if (index > 0) {
                                valueKey = valueKey.substring(index+1);
                            }
                            valueKey = valueKey.replaceAll("\\.", "_");
                            String value_text = (String)value.getValue();
                            if (StringUtils.isNotBlank(value_text) && value_text.startsWith("${")) {
                                value_text="";
                            }else{
                                configData.put(valueKey, value_text);
                            }

                        }
                    }
                }
            }
        }

        return getConfigureHTML(catatlogId, configData);
    }
    
    /**
     * 获取配置HTML内容
     * @param catatlogId
     * @return
     */
    public static String getConfigureHTML(String catatlogId, Map<Object, Object> configValues) {
        if (catalogs.size() == 0) {
            initationCatalogList();
        }
        ConfigureCatalog catalog = catalogs.get(catatlogId);
        if (catalog != null) {
            Config config = paserConfigureTemplate(catalog.getConfig(), configValues);
            return config.toHTML();
        } else {
            return "没有找到此配置内容";
        }
    }
    
    public static void generateTemplateFile(final Map<String, String> params, String catalogId) {
        ConfigureCatalog catalog = catalogs.get(catalogId);
        if (catalog != null) {
            try {
                Map<Object, Object> existsConfigData = readOriDataFromFile();
                if (null == existsConfigData) {
                    existsConfigData = PersistenceService.loadAllOriConfigDataFromDB();
                }
                existsConfigData.putAll(params);
                saveOriDataToFile(existsConfigData);
                LOG.info("save original data to file success: " + catalogId);
            } catch (Exception e) {
                LOG.error("save original data to file failed: " + catalogId, e);
            }
            
            List<Generate> generates = catalog.getTemplates();
            boolean failure = false;
            for (int i = 0; i < generates.size(); i++) {
                Generate generate = generates.get(i);
                boolean transToUnicode = false;
                try {
                    String template = generate.getTemplate();
                    String templatePath = getTemplateFilePath(template);
                    String templateContent = readFromFile(new File(templatePath));
                    
                    if (null == templateContent) {
                        failure = true;
                        continue;
                    }
                    if (template.endsWith(".properties")) {
                        saveValuedContentToZooKeeper(template, templateContent, params);
                        transToUnicode = true;
                    }
                    
                    String targetFileName = ConfigUtil.getFileNameWithTenant(template);
                    String valuedContent = genValuedContent(templateContent, params, transToUnicode);
                    writeToFile(new File(EnvironmentUtils.getRuntimeConfigPath(), targetFileName), valuedContent);
                    generate.setModifyTime(System.currentTimeMillis());
                } catch (Exception e) {
                    LOG.error("生成文件失败", e);
                    failure = true;
                }
            }
            if (failure) {
                catalog.setStatus(1);
            } else {
                catalog.setStatus(2);
            }
        }
    }
    
    private static void saveValuedContentToZooKeeper(String filePath, String templateContent,
            Map<String, String> configData) throws IOException {
        
        String valuedContent = genValuedContent(templateContent, configData, false);
        Properties prop = new Properties();
        StringReader reader = null;
        
        try {
            reader = new StringReader(valuedContent);
            prop.load(reader);
        } finally {
            close(reader);
        }
        
        // 类别
        String category = "";
        // 文件
        String file = "";
        
        if (filePath != null) {
            String[] fileNameParts = filePath.split("/");
            if (fileNameParts.length == 3) {
                category = fileNameParts[1];
                file = fileNameParts[2];
            } else if (fileNameParts.length == 2) {
                category = fileNameParts[0];
                file = fileNameParts[1];
            }
        }
        
        if (StringUtils.isNotBlank(category)) {
            String tenantId = DataContext.getTenantId();
            Map<String, Object> configuration = PersistenceService.getConfigDataFromZooKeeper(category, tenantId);
            configuration.put(file, prop);
            PersistenceService.saveConfigDataToZooKeeper(category, tenantId, configuration);
        }
    }
    
    private static String getLocalOriDataFilePath() {
        StringBuilder pathBuilder = new StringBuilder();
        pathBuilder.append(EnvironmentUtils.getRuntimeConfigPath());
        pathBuilder.append('/').append(ConfigUtil.getNamedTenantId()).append("Data");
        return pathBuilder.toString();
    }
    
    private static void saveOriDataToFile(Map<Object, Object> params) throws IOException {
        String filePath = getLocalOriDataFilePath();
        File file = new File(filePath);
        StringBuilder sBuilder = new StringBuilder(4096);
        sBuilder.append("解析时的entry分隔符为\\r\\n#####\\r\\n，文档尾部也有一个分隔符，如需手动编辑本文件，请勿编辑分隔符").append(ORI_DATA_SEPERATOR);
        for (Entry<Object, Object> paramEntry : params.entrySet()) {
            sBuilder.append(paramEntry.getKey()).append('=').append(paramEntry.getValue())
                    .append(ORI_DATA_SEPERATOR);
        }
        String content = sBuilder.toString();
        writeToFile(file, content);
    }
    
    public static Map<Object, Object> readOriDataFromFile() {
        String filePath = getLocalOriDataFilePath();
        File file = new File(filePath);
        if (!file.exists()) {
            if (!ConfigUtil.isNoTenant()) {
                file = new File(EnvironmentUtils.getRuntimeConfigPath(), "data");
            }
        }
        
        String content = readFromFile(file);
        if (null == content) {
            return null;
        }
        Map<Object, Object> data = new HashMap<Object, Object>();
        int sepLen = ORI_DATA_SEPERATOR.length();
        int startIdx = 0;
        int endIdx = 0;
        while (-1 != (endIdx = content.indexOf(ORI_DATA_SEPERATOR, startIdx))) {
            String entry = content.substring(startIdx, endIdx);
            int inEntrySepIdx = entry.indexOf('=');
            if (-1 != inEntrySepIdx) {
                data.put(entry.substring(0, inEntrySepIdx), entry.substring(inEntrySepIdx + 1));
            }
            startIdx = endIdx + sepLen;
        }
        if (startIdx < content.length()) {
            String entry = content.substring(startIdx);
            int inEntrySepIdx = entry.indexOf('=');
            if (-1 != inEntrySepIdx) {
                data.put(entry.substring(0, inEntrySepIdx), entry.substring(inEntrySepIdx + 1));
            }
        }
        return data;
    }
    
    private static String readFromFile(File file) {
        if (!file.exists()) {
            return null;
        }
        FileInputStream fis = null;
        InputStreamReader isr = null;
        StringBuilder sBuilder = new StringBuilder(4096);
        char[] buffer = new char[4096];
        int len = 0;
        try {
            fis = new FileInputStream(file);
            isr = new InputStreamReader(fis, "UTF-8");
            while (-1 != (len = isr.read(buffer))) {
                sBuilder.append(buffer, 0, len);
            }
            return sBuilder.toString();
        } catch (Exception e) {
            LOG.error("read from file failed: " + file.getAbsolutePath(), e);
            return null;
        } finally {
            close(isr);
            close(fis);
        }
    }
    
    private static void writeToFile(File file, String content) throws IOException {
        File dir = file.getParentFile();
        if (!dir.isDirectory() && !dir.mkdirs()) {
            throw new IOException("create dir failed: " + dir.getAbsolutePath());
        }
        FileOutputStream fos = null;
        OutputStreamWriter osw = null;
        try {
            fos = new FileOutputStream(file);
            osw = new OutputStreamWriter(fos, "UTF-8");
            osw.write(content);
            osw.flush();
        } finally {
            close(osw);
            close(fos);
        }
    }
    
    private static void close(Closeable io) {
        if (null != io) {
            try {
                io.close();
            } catch (Exception e) {
                LOG.error("close io failed", e);
            }
        }
    }
    
    private static Map<String, String> transToUnicodeMap(Map<String, String> params) {
        Map<String, String> newParams = new HashMap<String, String>();
        for (Entry<String, String> paramEntry : params.entrySet()) {
            newParams.put(paramEntry.getKey(), StringUtil.toPropertiesValue(paramEntry.getValue()));
        }
        return newParams;
    }
    
    private static String genValuedContent(String templateContent, Map<String, String> configData,
            boolean transToUnicode) {
        if (transToUnicode) {
            configData = transToUnicodeMap(configData);
        }
        return StrSubstitutor.replace(templateContent, configData, "${", "}");
    }
    
    private static Config paserConfigureTemplate(String templatePath, Map<Object, Object> dataMap) {
        
        try {
            
            templatePath = getTemplateFilePath(templatePath);
            
            ConfigureParser parser = new ConfigureParser();
            parser.setDataStore(dataMap);
            
            Config config = parser.parserConfigureFromFullPath(templatePath);
            
            return config;
        } catch (Exception e) {
            LOG.error("读取配置模板" + templatePath + "出错", e);
        }
        return null;
    }
    
}
